#ifndef __M_SESSION_H__
#define __M_SESSION_H__
#include "util.hpp"
#include <unordered_map>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>


// 使用cookie和session进行http在短链接通信的情况下进行用户状态管理。
// 但是这个服务器上管理的session都会有过期时间，
// 超过时间了，就会将对应的session删除每次客户端与服务端的通信都需要延长session的过期时间

// session模块:保存客户端的用户状态信息
//     1.有自己的标识: ssid
//     2.用户的状态信息。
//     3.用户ID



typedef enum{
    UNLOGIN,
    LOGIN
}ss_statu;

class session{
private:
    uint64_t _ssid;              // 标识符
    uint64_t _uid;               // session对应的用户id
    ss_statu _statu;             // 用户状态：未登录，已登录
    wsserver_t::timer_ptr _tp;   // session关联的定时器
public:
    session(uint64_t ssid) :_ssid(ssid){DLOG("session %p 被创建！", this);}
    ~session(){DLOG("session %p 被释放！", this);}
    uint64_t ssid(){return _ssid;}
    void set_statu(ss_statu statu){_statu = statu;}
    void set_user(uint64_t uid){_uid = uid;}
    uint64_t get_user(){return _uid;}
    bool is_login(){return (_statu == LOGIN);}
    void set_timer(const wsserver_t::timer_ptr &tp){_tp = tp;}
    wsserver_t::timer_ptr& get_timer(){return _tp;}
};



// session管理的数据:
//     1.ssid的计数器
//     2.互斥锁
//     3.管理器unordered_map<ssid, session_ptr>
//     4. websocket的server的对象--用于添加定时任务

// session管理:
//     1.创建session
//     2.为session设置过期时间
//     3.获取session
//     4.销毁session(过期自动被销毁)


#define SESSION_TIMEOUT 30000     // 30 秒
#define SESSION_FOREVER -1        // 永久存在
using session_ptr = std::shared_ptr<session>;
class session_manager{
private:
    uint64_t _next_ssid;
    std::mutex _mutex;
    std::unordered_map<uint64_t, session_ptr> _session;
    wsserver_t* _server;
public:
    session_manager(wsserver_t* srv) :_next_ssid(1), _server(srv){
        DLOG("session 管理器初始化完毕！");
    }
    ~session_manager(){
        DLOG("session 管理器初销毁完毕！");
    }
    // 创建一个会话
    session_ptr create_session(uint64_t uid, ss_statu statu){
        // 得加锁哦
        std::unique_lock<std::mutex> lock(_mutex);
        session_ptr ssp(new session(_next_ssid));
        ssp->set_statu(statu);
        ssp->set_user(uid);
        _session.insert(std::make_pair(_next_ssid, ssp));
        _next_ssid++;
        return ssp;
    }

    // 
    void append_session(const session_ptr &ssp){
        // 得加锁哦
        std::unique_lock<std::mutex> lock(_mutex);
        _session.insert(std::make_pair(ssp->ssid(), ssp));
    }

    // 通过 ssid 查找 session 会话
    session_ptr get_session_by_ssid(uint64_t ssid){
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _session.find(ssid);
        if(it == _session.end()){
            return session_ptr();
        }
        return it->second;
    }

    // 销毁 session
    void remove_session(uint64_t ssid){
        // 得加锁哦
        std::unique_lock<std::mutex> lock(_mutex);
        _session.erase(ssid);
    }

    // 给session设置一个过期时间
    // --> 注意：tp->cancel(); 不是立即被执行的
    void set_session_expire_time(uint64_t ssid, int ms){
        // 依赖于websocketpp的定时器来完成session生命周期的管理。
        // 登录之后，创建session，session需要在指定时间无通信后删除
        // 但是进入游戏大厅，或者游戏房间，这个session就应该永久存在
        // 等到退出游戏大厅，或者游戏房间，这个session应该被重新设置为临时，在长时间无通信后被删除
        session_ptr ssp = get_session_by_ssid(ssid);
        if(ssp.get() == nullptr) return;

        wsserver_t::timer_ptr tp = ssp->get_timer();
        // 1. 在session永久存在的情况下，设置永久存在
        if(tp.get() == nullptr && ms == SESSION_FOREVER) return;
        else if(tp.get() == nullptr && ms != SESSION_FOREVER){
            // 2. 在session永久存在的情况下，设置指定时间之后被删除的定时任务
            wsserver_t::timer_ptr temp_tp = _server->set_timer(ms, std::bind(&session_manager::remove_session, this, ssid));
            ssp->set_timer(temp_tp);
        }else if(tp.get() != nullptr && ms == SESSION_FOREVER){
            // 3. 在session设置了定时删除的情况下，将session设置为永久存在
            // 删除定时任务--- stready_timer删除定时任务会导致任务直接被执行
            tp->cancel();  // 因为取消定时任务并非不是立即被取消的

            //因此重新给session管理器中，添加一个session信息, 且添加的时候需要使用定时器，而不是立即添加
            ssp->set_timer(wsserver_t::timer_ptr());   //将session关联的定时器设置为空
            _server->set_timer(0, std::bind(&session_manager::append_session, this, ssp));
            // 得用上面的写法的哦
            // _session.insert(std::make_pair(ssp->ssid(), ssp));
        }else{
            // 4. 在session设置了定时删除的情况下，将session重置删除时间。
            if(tp.get() != nullptr && ms != SESSION_FOREVER){
                tp->cancel();  // 因为这个取消定时任务并不是立即取消的
                ssp->set_timer(wsserver_t::timer_ptr());
                _server->set_timer(0, std::bind(&session_manager::append_session, this, ssp));

                // 重新给session添加定时销毁任务
                wsserver_t::timer_ptr tmp_tp  = _server->set_timer(ms, std::bind(&session_manager::remove_session, this, ssp->ssid()));
                // 重新设置session关联的定时器
                ssp->set_timer(tmp_tp);
            }
        }
    }
};

#endif
